package eerbaar.server.gamebuilder.config;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import eerbaar.server.gamebuilder.Builder;
import eerbaar.server.gamebuilder.IConfig;

public class ConfigLoader {

    private static final String GAME_XML = "game.xml";

    private static GameConfig loadedConfig;

    private IConfig createConfig(StartElement element) {

        IConfig result = null;

        Map<String, IConfig> map = getMappings();
        result = map.get(element.getName().getLocalPart());
        if (result == null) {
            result = new UnknownConfig();
        }

        return result;
    }

    private XMLEventReader createReader() throws FactoryConfigurationError,
            XMLStreamException, IOException {

        InputStream in = Builder.class.getClassLoader().getResourceAsStream(
                GAME_XML);

        Reader r = new InputStreamReader(in);
        BufferedReader breader = new BufferedReader(r);

        XMLEventReader reader = XMLInputFactory.newInstance()
                .createXMLEventReader(breader);

        return reader;
    }

    private Map<String, IConfig> getMappings() {

        Map<String, IConfig> map = new HashMap<String, IConfig>();
        map.put("action", new ActionConfig());
        map.put("actions", new ActionsConfig());
        map.put("area", new AreaConfig());
        map.put("complete", new CompleteConfig());
        map.put("element", new ElementConfig());
        map.put("game", new GameConfig());
        map.put("hint", new HintConfig());
        map.put("hints", new HintsConfig());
        map.put("image", new ImageConfig());
        map.put("level", new LevelConfig());
        map.put("levels", new LevelsConfig());
        map.put("message", new MessageConfig());
        map.put("messages", new MessagesConfig());
        map.put("prop", new PropConfig());
        map.put("setup", new SetupConfig());
        map.put("state", new StateConfig());
        map.put("states", new StatesConfig());
        map.put("when", new WhenConfig());
        map.put("whens", new WhensConfig());

        return map;
    }

    public GameConfig load() throws FactoryConfigurationError,
            XMLStreamException, IOException {

        if (loadedConfig != null) {
            return loadedConfig;
        }

        IConfig result = null;

        XMLEventReader reader = createReader();

        Deque<IConfig> stack = new LinkedList<IConfig>();
        while (reader.hasNext()) {

            XMLEvent event = reader.nextEvent();

            if (event.isStartDocument()) {
                continue;
            }

            if (event.isStartElement()) {

                IConfig parent = null;
                if (stack.size() > 0) {
                    parent = stack.peek();
                }

                StartElement element = event.asStartElement();
                System.out.println(element);
                IConfig config = createConfig(element);
                if (parent != null) {
                    String name = element.getName().getLocalPart();
                    parent.setConfig(name, config);
                }

                Iterator<?> i = element.getAttributes();
                while (i.hasNext()) {
                    Attribute a = (Attribute) i.next();
                    config.setProperty(a.getName().getLocalPart(), a.getValue());
                }

                stack.push(config);

            } else if (event.isEndElement()) {

                result = stack.pop();
                if (stack.size() == 0) {
                    break;
                }
            }
        }

        loadedConfig = (GameConfig) result;
        return loadedConfig;
    }

}
